package com.smap.common;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;

public class ResourceLoader implements EventListener {
	public static int CACHE_SIZE = 100;
	public int timeout = 20;
	//maybe a max thread per layer is better
	public int maxThread = 5;

	public Map<ImageElement, Request> currentThreads = new HashMap<ImageElement, Request>();

	private LinkedHashMap<String, ImageElement> cache = new LinkedHashMap<String, ImageElement>();

	private LinkedHashMap<String, ImageElement> getCache() {
		if (cache.size() > CACHE_SIZE) {
			String first = cache.keySet().iterator().next();
			cache.remove(first);
		}
		return cache;
	}

	Element loadingArea;
	LinkedList<Request> requestPool = new LinkedList<Request>();

	public Element getLoadingArea() {
		if (loadingArea == null) {
			loadingArea = DOM.createDiv();
			loadingArea.getStyle().setProperty("visibility", "hidden");
			loadingArea.getStyle().setProperty("position", "absolute");
			loadingArea.getStyle().setProperty("width", "1px");
			loadingArea.getStyle().setProperty("height", "1px");
			loadingArea.getStyle().setProperty("overflow", "hidden");
			Document.get().getBody().appendChild(loadingArea);
			Event.setEventListener(loadingArea, this);
		}
		return loadingArea;
	}

	public void update() {
		while (requestPool.size() > 0) {
			if (currentThreads.size() > maxThread) {
				break;
			}
			// grab one request and execute it
			Request request = requestPool.poll();
			Element area = getLoadingArea();
			ImageElement image = DOM.createImg().cast();
			image.setSrc(request.url);
			area.appendChild(image);
			Event.sinkEvents(image, Event.ONLOAD | Event.ONERROR);
			currentThreads.put(image, request);
		}
		// System.out.println(currentThreads.size());
	}

	public void loadImg(String url, Callback callback, String info) {
		ImageElement image = getCache().get(url);
		if (image != null) {
			callback.onLoad(image, info, requestPool.size(),
					currentThreads.size());
		} else {
			requestPool.add(new Request(url, callback, info));
			update();
			checkTimeout();
		}
	}

	public static interface Callback {
		public void onLoad(Object obj, String info, int inQueue, int running);
	}

	public static class Request {
		public String url;
		public Callback callback;
		public String info;
		long timeStamp;

		public Request(String url, Callback callback, String info) {
			super();
			this.url = url;
			this.callback = callback;
			this.info = info;
			this.timeStamp = new Date().getTime();
		}

	}

	private void checkTimeout() {
		for (ImageElement image : currentThreads.keySet()) {
			Request req = currentThreads.get(image);
			long idle = new Date().getTime() - req.timeStamp;
			if (idle > (timeout * 1000)) {
				image.setSrc("");
				currentThreads.remove(image);
				loadingArea.removeChild(image);
			}
		}
	}

	public void onBrowserEvent(Event event) {

		boolean success;
		if (Event.ONLOAD == event.getTypeInt()) {
			success = true;
		} else if (Event.ONERROR == event.getTypeInt()) {
			System.out.println("ERROR");
			success = false;
		} else {
			return;
		}
		if (!ImageElement.is(event.getCurrentEventTarget())) {
			return;
		}

		ImageElement image = ImageElement.as(Element.as(event
				.getCurrentEventTarget()));

		Request req = currentThreads.get(image);
		// remove it
		currentThreads.remove(image);
		loadingArea.removeChild(image);
		// return
		req.callback.onLoad(image, req.info, requestPool.size(),
				currentThreads.size());
		update();

		getCache().put(req.url, image);
	}

	private static ResourceLoader instance;

	public static ResourceLoader get() {
		if (instance == null) {
			instance = new ResourceLoader();
		}
		return instance;
	}

	public void clear() {
		requestPool.clear();
		for (ImageElement image : currentThreads.keySet()) {
			loadingArea.removeChild(image);
		}
		currentThreads.clear();
	}
}
